<!DOCTYPE html>
<html lang="zh-CN">

<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Canvas粒子连线效果</title>
	<style>
		* {
			margin: 0;
			padding: 0;
			box-sizing: border-box;
		}

		body {
			overflow: hidden;
			background: #0f1620;
			display: flex;
			justify-content: center;
			align-items: center;
			min-height: 100vh;
			font-family: 'Arial', sans-serif;
			color: #fff;
		}

		canvas {
			position: fixed;
			top: 0;
			left: 0;
		}

		.content {
			text-align: center;
			z-index: 10;
			padding: 2rem;
			border-radius: 10px;
			backdrop-filter: blur(5px);
			max-width: 600px;
		}

		h1 {
			margin-bottom: 1rem;
			color: #42dcf4;
			text-shadow: 0 0 10px rgba(66, 220, 244, 0.5);
		}

		p {
			margin-bottom: 1.5rem;
			line-height: 1.6;
		}

		.controls {
			display: flex;
			justify-content: center;
			gap: 1rem;
			margin-top: 1.5rem;
		}

		button {
			background: rgba(66, 220, 244, 0.2);
			border: 1px solid #42dcf4;
			color: white;
			padding: 0.5rem 1rem;
			border-radius: 5px;
			cursor: pointer;
			transition: all 0.3s;
		}

		button:hover {
			background: rgba(66, 220, 244, 0.4);
			box-shadow: 0 0 10px rgba(66, 220, 244, 0.5);
		}
	</style>
</head>

<body>
	<canvas id="particleCanvas"></canvas>
	<div class="content">
		<button id="addParticles">添加粒子</button>
		<button id="reduceParticles">减少粒子</button>
		<button id="changeColor">改变颜色</button>
	</div>

	<script>
		/** @type {HTMLCanvasElement} */ // 提示补全

		// 获取Canvas元素和上下文
		const canvas = document.getElementById('particleCanvas');
		const ctx = canvas.getContext('2d');

		// 设置Canvas尺寸为窗口大小
		canvas.width = window.innerWidth;
		canvas.height = window.innerHeight;

		// 粒子数组
		const points = [];

		// 配置参数
		let config = {
			pointCont: 1,
			distance: 150, // 连线最大距离
			particleSize: 3, // 粒子大小
			baseHue: 200, // 基础色相
			mouseRadius: 100, // 鼠标半径范围
			lineWidth: 0.5,
		};

		// 鼠标位置
		let mouse = {
			x: null,
			y: null
		};

		// 颜色方案
		const colorSchemes = [
			{ baseHue: 180, name: "蓝色科技" }, // 蓝色
			{ baseHue: 320, name: "紫色梦幻" }, // 紫色
			{ baseHue: 60, name: "金色光芒" },  // 黄色
			{ baseHue: 120, name: "绿色生态" }, // 绿色
			{ baseHue: 0, name: "红色激情" }    // 红色
		];
		let colorSchemeIndex = 0;

		// 粒子类
		class Point {
			constructor() {
				this.x = Math.random() * canvas.width
				this.y = Math.random() * canvas.height
				this.r = config.particleSize;
				this.speedX = Math.random() * 2 - 1;
				this.speedY = Math.random() * 2 - 1;
				const baseHue = colorSchemes[Math.floor(Math.random() * colorSchemes.length)].baseHue
				this.color = `hsl(${baseHue}, 100%, 70%)`;
			}
			draw() {
				ctx.fillStyle = this.color
				ctx.beginPath()
				ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2)
				ctx.fill()
				ctx.closePath()
			}
			update() {
				this.x += this.speedX;
				this.y += this.speedY;

				if (this.x < 0) {
					this.x = 0
					this.speedX = Math.abs(this.speedX)
				} else if (this.x > canvas.width) {
					// 防止改变屏幕时，粒子出界回不来
					this.x = canvas.width
					this.speedX = -Math.abs(this.speedX)
				}

				if (this.y < 0) {
					this.y = 0
					this.speedY = Math.abs(this.speedY)
				} else if (this.y > canvas.height) {
					// 防止改变屏幕时，粒子出界回不来
					this.y = canvas.height
					this.speedY = -Math.abs(this.speedY)
				}

				// TODO 鼠标交互 - 排斥力 这里的Math相关的api我不是太懂
				if (mouse.x !== null && mouse.y !== null) {
					const dx = mouse.x - this.x;
					const dy = mouse.y - this.y;
					// 计算鼠标和原子直线距离距离 ， Math.sqrt()开平方
					const distance = Math.sqrt(dx ** 2 + dy ** 2)
					if (distance < config.mouseRadius) {
						// 计算坐标与 X 轴之间的角度的弧度 Math.atan2()，弧度是圆角对应的圆周长与半径的比值
						const angle = Math.atan2(dy, dx);
						console.log(angle);
						// 排斥力，距离越近，力道越大
						const force = ((config.mouseRadius - distance) / config.mouseRadius) * 10;
						// FIXME 这里的力道方向我也不是很懂
						// 让粒子远离鼠标
						this.x -= Math.cos(angle) * force;
						this.y -= Math.sin(angle) * force;
					}
				}
				this.draw()
			}

		}

		// 初始化
		function initPoint() {
			for (let i = 0; i < config.pointCont; i++) {
				points.push(new Point())
			}
		}

		// 画线
		function drawLines() {
			for (let i = 0; i < points.length; i++) {
				for (let j = i + 1; j < points.length; j++) {
					const dx = points[i].x - points[j].x;
					const dy = points[i].y - points[j].y;
					// 计算距离
					const distance = Math.sqrt(dx * dx + dy * dy);
					if (distance < config.distance) {
						ctx.beginPath()
						ctx.moveTo(points[i].x, points[i].y)
						ctx.lineTo(points[j].x, points[j].y)
						ctx.strokeStyle = `rgba(255,255,255,${1 - distance / config.distance})`
						ctx.lineWidth = config.lineWidth
						ctx.stroke()
						ctx.closePath()
					}
				}
			}
		}

		// 动画循环
		function animation() {
			ctx.clearRect(0, 0, canvas.width, canvas.height)

			points.forEach(p => {
				p.update()
			})

			// 绘制连线
			drawLines()

			requestAnimationFrame(animation)
		}

		// 窗口大小改变
		window.addEventListener('resize', () => {
			canvas.width = window.innerWidth;
			canvas.height = window.innerHeight;
		});
		// 鼠标移动
		window.addEventListener('mousemove', (e) => {
			mouse.x = e.offsetX
			mouse.y = e.offsetY
		});

		// 鼠标离开事件
		canvas.addEventListener('mouseout', function () {
			mouse.x = null;
			mouse.y = null;
		});

		// 添加粒子按钮
		document.getElementById('addParticles').addEventListener('click', function () {
			config.pointCont += 20;
			for (let i = 0; i < 20; i++) {
				points.push(new Point());
			}
		});

		// 减少粒子按钮
		document.getElementById('reduceParticles').addEventListener('click', function () {
			if (config.pointCont > 20) {
				config.pointCont -= 20;
				points.splice(-20);
			}
		});

		// 改变颜色按钮
		document.getElementById('changeColor').addEventListener('click', function () {
			colorSchemeIndex = (colorSchemeIndex + 1) % colorSchemes.length;
			config.baseHue = colorSchemes[colorSchemeIndex].baseHue;

			// 更新所有粒子的颜色
			for (let i = 0; i < points.length; i++) {
				points[i].color = `hsl(${config.baseHue}, 100%, 70%)`;
			}

			document.querySelector('h1').textContent = `Canvas粒子连线效果 - ${colorSchemes[colorSchemeIndex].name}`;
		});


		initPoint()
		animation()
	</script>
</body>

</html>